package es.xpt.ysf.view.collections;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import es.xpt.ysf.logging.Log;
import es.xpt.ysf.logging.LogManager;
import es.xpt.ysf.utils.JNDIName;
import es.xpt.ysf.utils.ServiceLocator;
import es.xpt.ysf.utils.ServiceLocatorException;
import es.xpt.ysf.view.model.UserBean;
import es.xpt.ysf.web.Constants;

@XmlAccessorType(XmlAccessType.FIELD)
public class ResolvedMethod {
	@XmlAttribute
	private Boolean cache;
	private String methodName;
	private String jndiName;
	private String className;
	@XmlElementWrapper ( name = "params")
	@XmlElement ( name = "param")
	private List<String> params;
	
	@XmlTransient
	protected List<Object> args = new ArrayList<Object>(3);
	
	public ResolvedMethod() {
		super();
		params = new ArrayList<String>(3);
	}
	
	public Object staticInvoke() throws ServiceLocatorException {
		Log log = LogManager.getLog(this.getClass());
		try {
			Class<?> c = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
			
			Class<?>[] paramsArray = new Class[params.size()];

			for ( int i = 0 ; i < paramsArray.length; i++ ) 
				paramsArray[i] = Class.forName(params.get(i), true, Thread.currentThread().getContextClassLoader());
			
			Method m = c.getMethod(methodName, paramsArray);
			return m.invoke(null, args.toArray());
			
		} catch (ClassNotFoundException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (SecurityException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (NoSuchMethodException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (IllegalArgumentException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (IllegalAccessException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (InvocationTargetException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		}
	}
	
	public Object invoke() throws ServiceLocatorException {
		Log log = LogManager.getLog(this.getClass());
		if ( log.isDebugEnabled() )
			log.debug("COLLECTIONS[INI] Invocando a: " + methodName + " de " + jndiName);
		
		UserBean ub = ((UserBean)((HttpSession)FacesContext.getCurrentInstance()
	    			.getExternalContext().getSession(false)).getAttribute(Constants.USER_INFORMATION.getValue()));
		
		String key = null;
		Cache mycache = null;
		if ( cache!=null && cache ) {
			mycache = CacheManager.getInstance().getCache("collections@" + ub.getApplication());
			if ( mycache!=null ) {
				key = getCacheKey(ub.getLocale().toString());
				Element e = mycache.get( key );
				if ( e!=null && e.getObjectValue()!=null ) {
					return e.getObjectValue();
				}
			}
		}
		
		ServiceLocator sl = new ServiceLocator(ub);
		Object o = sl.getLocal(new JNDIName(jndiName));
	    
		try {
			Class<?>[] paramsArray = new Class[params.size()];

			for ( int i = 0 ; i < paramsArray.length; i++ ) 
				paramsArray[i] = Class.forName(params.get(i), true, Thread.currentThread().getContextClassLoader());
			Method m = o.getClass().getMethod(methodName, paramsArray);
			Object result = m.invoke(o, args.toArray());
			if ( key!=null && mycache!=null ) {
				mycache.put(new Element(key, result));
			}
			return result;
			
		} catch ( ClassNotFoundException e ) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (SecurityException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (NoSuchMethodException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (IllegalArgumentException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (IllegalAccessException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} catch (InvocationTargetException e) {
			log.error(e.getMessage(), e);
			throw new ServiceLocatorException(e);
		} finally {
			if ( log.isDebugEnabled() )
				log.debug("COLLECTIONS[FIN] Invocando a: " + methodName + " de " + jndiName );
		}
		
	}

	
	public boolean isReady() {
		return params.size() == args.size();
	}

	public void clearArgs() {
		args.clear();
	}
		
	public void addArg(Object o) {
		args.add(o);
	}
	
	private String getCacheKey(String locale) {
		
		if ( !args.isEmpty() ) {
			StringBuilder sb = new StringBuilder(jndiName+"#"+methodName);
			for ( Object o: args ) {
				sb.append(o.toString());
			}
			sb.append("#"+locale);
			return sb.toString();
		} else {
			return jndiName+"#"+methodName+"#"+locale;
		}
	}


	/**
	 * @return the params
	 */
	public Collection<String> getParams() {
		return params;
	}

	/**
	 * @param params the params to set
	 */
	public void setParams(List<String> params) {
		this.params = params;
	}



	/**
	 * @return the methodName
	 */
	public String getMethodName() {
		return methodName;
	}



	/**
	 * @param methodName the methodName to set
	 */
	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}



	/**
	 * @return the jndiName
	 */
	public String getJndiName() {
		return jndiName;
	}



	/**
	 * @param jndiName the jndiName to set
	 */
	public void setJndiName(String jndiName) {
		this.jndiName = jndiName;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public String getClassName() {
		return className;
	}

	public void setCache(Boolean cache) {
		this.cache = cache;
	}

	public Boolean getCache() {
		return cache;
	}
	
	
}
